import {NgModule,Component,OnChanges,ViewChild,AfterContentInit,Input,QueryList,TemplateRef,ContentChildren,ElementRef,Output,EventEmitter,ViewContainerRef, OnInit,OnDestroy,ComponentFactoryResolver, Directive} from '@angular/core';
import {CommonModule} from '@angular/common';
import { FormGroup, FormBuilder,ReactiveFormsModule,Validators ,AbstractControl , ValidationErrors} from '@angular/forms';

import { Observable } from "rxjs/Observable";
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/observable/timer';

import { HttpInterceptorService } from "../../common/http-interceptor.service";

import { ValidatorsMap } from "../../common/check";
import {SharedModule,PrimeTemplate} from '../common/shared';
import { MessageModule } from '../message/message';
import { InputTextModule } from '../inputtext/inputtext';
import { DropdownModule } from '../dropdown/dropdown';
import { MultiSelectModule } from '../multiselect/multiselect';
import { CalendarModule } from '../calendar/calendar';
import { InputTextareaModule } from '../inputtextarea/inputtextarea';
import { CheckboxModule } from '../checkbox/checkbox';
import { RadioButtonModule } from '../radiobutton/radiobutton';
import { FileUploadModule } from '../fileupload/fileupload';
import { TreeModule } from '../tree/tree';



/** 
* @type    表单单元组件【to Form】
* @author  最后修改者:刘天广 
* @version 最后修改版本:v-ng4-1.1.0
* @description ：
           onStatus 对外事件驱动 焦点事件、失去焦点事件
*/ 

@Component({
    selector: 'form-input',
    template: `
    <div [formGroup]="group">
        <input type="text" [formControl]="group?.get(config.name)" (blur)="blurEven()" [numberType]="config.numberType" (focus)="focusEven()" [maxlength]="config.maxlength"  [formControlName]="config.name"  [attr.placeholder]="config.placeholder || '请输入'+(theme == 'table'?'':config.title)"  pInputText >
    </div>
  `
})
export class FormInputComponent {

    config: any;

    group: FormGroup;

    theme:string;

    @Output() onStatus: EventEmitter<any> = new EventEmitter<any>();

    focusEven(){
      this.onStatus.emit(true)
    }

    blurEven(){
      this.onStatus.emit(false)
    }
}


@Component({
    selector: 'form-textarea',
    template: `
    <div [formGroup]="group">
        <textarea rows="5" type="text" (blur)="blurEven()" (focus)="focusEven()" [maxlength]="config.maxlength" class="width-full v-top" [formControlName]="config.name"  [attr.placeholder]="config.placeholder || '请输入'+(theme == 'table'?'':config.title)"  pInputTextarea ></textarea>
        <div *ngIf="config?.residue" class="text-right opacity06 residue">还可输入{{(config.maxlength || 999) - (getValue()?.length || 0)}}个字</div>
    </div>
  `
})
export class FormTextareaComponent {

    config: any;

    group: FormGroup;

    theme:string;

    getValue(){
      if(!this.group || !this.config) return;
      return this.group.get(this.config.name).value
    }

    @Output() onStatus: EventEmitter<any> = new EventEmitter<any>();

    focusEven(){
      this.onStatus.emit(true)
    }

    blurEven(){
      this.onStatus.emit(false)
    }
}


@Component({
    selector: 'form-checkbox',
    template: `
    <div [formGroup]="group">
        <p-checkbox [formControl]="group?.get(config.name)" *ngFor="let item of config.options;index as index"  [value]="item.value" [inputId]="config.name + index" [label]="item.label" [formControlName]="config.name"></p-checkbox>
    </div>
  `
})
export class FormCheckboxComponent {

    config: any;

    group: FormGroup;

    theme:string;

    @Output() onStatus: EventEmitter<any> = new EventEmitter<any>();

    focusEven(){
      this.onStatus.emit(true)
    }

    blurEven(){
      this.onStatus.emit(false)
    }
}


@Component({
    selector: 'form-radioButton',
    template: `
    <div [formGroup]="group">
        <p-radioButton *ngFor="let item of config.options;index as index" [name]="config.name" [value]="item.value" [label]="item.label" [formControlName]="config.name"></p-radioButton>
    </div>
  `
})
export class FormRadioButtonComponent {

    config: any;

    group: FormGroup;

    theme:string;

    @Output() onStatus: EventEmitter<any> = new EventEmitter<any>();

    focusEven(){
      this.onStatus.emit(true)
    }

    blurEven(){
      this.onStatus.emit(false)
    }
}

@Component({
    selector: 'form-calendar',
    template: `
    <div [formGroup]="group" [class]="config.class">
         <p-calendar (onFocus)="focusEven()" (onClose)="blurEven()" [maxDate]="config.maxDate" [minDate]="config.minDate" [formControlName]="config.name" [dataType]="config.dataType || 'string'" [maxDate]="config.maxDate" [minDate]="config.minDate" [placeholder]="config.placeholder || '请选择'+(theme == 'table'?'':config.title)"  styleClass="p-calendar" inputStyleClass="height-full"></p-calendar>
    </div>
  `
})
export class FormCalendarComponent {

    config: any;

    theme:string;

    group: FormGroup;

    @Output() onStatus: EventEmitter<any> = new EventEmitter<any>();

    focusEven(){
      this.onStatus.emit(true)
    }

    blurEven(){
      this.onStatus.emit(false)
    }
}

@Component({
    selector: 'form-dropdown',
    template: `
    <div [formGroup]="group">
        <p-dropdown (onPanelShow)="focusEven()" [cancleLabel]="config.cancleLabel" (onPanelHide)="blurEven()" [formControlName]="config.name" [justValue]="config.justValue" [dataKey]="config.dataKey" [optionLabel]="config.optionLabel" [placeholder]="config.placeholder || '请选择'+(theme == 'table'?'':config.title)" styleClass="width-full" [options]="config.options"></p-dropdown>
    </div>
  `
})

export class FormDropdownComponent {

    config: any;

    group: FormGroup;

    theme:string;

    @Output() onStatus: EventEmitter<any> = new EventEmitter<any>();

    focusEven(){
      this.onStatus.emit(true)
    }

    blurEven(){
      this.onStatus.emit(false)
    }
}

@Component({
    selector: 'form-tree',
    template: `
    <div [formGroup]="group">
      <p-treeselect (onFocus)="focusEven()" (onBlur)="blurEven()" [formControlName]="config.name" 
      [value]="config.options ||[]" [dataKey]="config.dataKey" [selectionMode]="config.selectionMode || 'single'" [placeholder]="config.placeholder || '请选择'+(theme == 'table'?'':config.title)"></p-treeselect>
    </div>
  `
})

export class FormTreeComponent {

    config: any;

    theme:string;

    group: FormGroup;

    @Output() onStatus: EventEmitter<any> = new EventEmitter<any>();

    focusEven(){
      this.onStatus.emit(true)
    }

    blurEven(){
      this.onStatus.emit(false)
    }
}

@Component({
    selector: 'form-multiSelect',
    template: `
    <div [formGroup]="group">
        <p-multiSelect  (onPanelShow)="focusEven()" (onPanelHide)="blurEven()" [formControlName]="config.name"  [dataKey]="config.dataKey" [optionLabel]="config.optionLabel" [defaultLabel]="config.placeholder || '请选择'+(theme == 'table'?'':config.title)" styleClass="width-full" [options]="config.options"></p-multiSelect>
    </div>
  `
})

export class FormMultiSelectComponent {

    config: any;

    theme:string;

    group: FormGroup;

    @Output() onStatus: EventEmitter<any> = new EventEmitter<any>();

    focusEven(){
      this.onStatus.emit(true)
    }

    blurEven(){
      this.onStatus.emit(false)
    }
}

/** 
* 
* @type    指令-创建组件
* @author  最后修改者:刘天广 
* @version 最后修改版本:v-ng4-1.1.0
* @description
*
*/ 
export const FormComponents = [
    FormInputComponent,
    FormCalendarComponent,
    FormDropdownComponent,
    FormTextareaComponent,
    FormCheckboxComponent,
    FormMultiSelectComponent,
    FormTreeComponent,
    FormRadioButtonComponent
  ]
export const FormRequiredMap = {
  'input': '请输入',
  'date':'请选择',
  'textarea':'请输入',
  'checkbox':'请选择',
  'radiobutton':'请选择',
  'multiselect':'请选择',
  'tree':'请选择',
  'dropdown': '请选择',
}  
//组件 map
export const FormComponentsMap:any = {
    'input': FormInputComponent,
    'date':FormCalendarComponent,
    'textarea':FormTextareaComponent,
    'checkbox':FormCheckboxComponent,
    'radiobutton':FormRadioButtonComponent,
    'multiselect':FormMultiSelectComponent,
    'tree':FormTreeComponent,
    'dropdown': FormDropdownComponent
}


@Directive({
    selector: '[reactiveForm]'
  })
  
export class ReactiveFormDirective implements OnInit,OnDestroy {
  
    @Input() group: FormGroup;

    @Input() validationMessages:any;

    @Input() Index:number;

    @Input() theme:string;

    @Input() readOnly:string;

    _errorMessages:any;

    _submiteed:boolean;

    _config: any;

    component: any;
        
    @Input() get config(): any {
      return this._config;
    }

    set config(config){
      this._config = config;
    }

    @Input() get errorMessages(): any {
      return this._errorMessages;
   }

   set errorMessages(errors){
      this._errorMessages = errors;
      if(this.component){this.component.instance.errorMessages = this.errorMessages;}
   }

    constructor(private resolver: ComponentFactoryResolver,private container: ViewContainerRef,private httpService:HttpInterceptorService,private fb: FormBuilder) {}
    
    ngOnInit() {
      if(this.config.template || this.config.component == 'label'){return}
      if(!this.config.name || !this.config.component){throw "if this item is not template or formArray,the property 'name' and 'component' is required" }
      this.addControl();
      const control = this.group.get(this.config.name);
      const component = FormComponentsMap[this.config.component];
      if(!component){throw 'the component:'+ this.config.component +' is not found,' + 'if you need a new Component Please contact ltg '}
      const factory = this.resolver.resolveComponentFactory<any>(component);

      this.component = this.container.createComponent(factory);
      this.component.instance.config = this.config;
      this.component.instance.group = this.group;
      this.component.instance.theme = this.theme;
    
      control.valueChanges.subscribe(data =>this.onValueChanged(data))

      if(!this.component.instance.onStatus){return}
      this.component.instance.onStatus.subscribe(val=>{
        this.config.focusStatus = val;
        const control = this.group.get(this.config.name);
        if(val && !this._errorMessages[this.config.name] && this.configRequired() && control.errors && control.errors.required){
          this._errorMessages[this.config.name] = {required:FormRequiredMap[this.config.component] + this.config.title}
        }
        // if(this.config.repeatUrl && control.valid &&!val){
        //   control.setErrors({repeat:'true'});
        //   let repeatParam = {[this.config.name]:control.value}  
        //   if(this.config.repeatParam){repeatParam = Object.assign(repeatParam,this.config.repeatParam)}
        //   this.httpService.post(this.config.repeatUrl,repeatParam).then(res=>{
        //     if(res.result){
        //       control.setErrors(null) 
        //     }else{
        //       this._errorMessages[this.config.name] = {repeat:`该${this.config.title}已被占用`}
        //     }           
        //   })
        // }
      })
    }

    ngOnDestroy(){
      if(this.config && this.group){
        this.group.removeControl(this.config.name);
        this._errorMessages && (this._errorMessages[this.config.name] = undefined)
      }
    }

    configRequired(){
      return this.config.validators && this.config.validators.includes('required')
    }

    getValidators(){
      const config = this.config;
      let   validators:any = null;
      if(config.validators){
        validators = [];
        config.validators.map(val=>validators = [...validators,ValidatorsMap[val]]); 
        if(!validators || validators.length==0)
          validators = null
      }
      return validators
    }

        //增加 表单组
    addControl(){
      const group = this.group;
      const config = this.config;
      let   index = this.Index;

      if(config.url){this.getData(config)}

      let newControl = this.fb.control({value:config.defaultValue || '',disabled:config.disabled},this.getValidators(),this.config.repeatUrl?this.repeatAsyncValidator.bind(this):null)
      group.addControl(config.name, newControl);
      if(config.output){
        newControl.valueChanges.subscribe(data => config.output(data,group,index))
      }
      if(config.output && config.defaultValue && config.runOutputWhenValue){
        config.output(config.defaultValue,group,index)
      }
    }

    repeatAsyncValidator(ctrl: AbstractControl): Observable<ValidationErrors|null> {
      let repeatParam = {[this.config.name]:this.group.get(this.config.name).value} ; 
      if(this.config.repeatParam){repeatParam = Object.assign(repeatParam,this.config.repeatParam)};
      

      return Observable.timer(10).switchMap(()=> {
        if(this.config.focusStatus || !ctrl.value || this.readOnly){
          return Observable.of(null);
        }
        return this.httpService.obsPost(this.config.repeatUrl,repeatParam)
        .map(resp => {
          if(resp.result){
            return null
          }else {
            this._errorMessages[this.config.name] = {repeat:`该${this.config.title}已被占用`}
            return {repeat:'true'}
          }  
        })
      });
    }

/**
 * 
 * @description 获取数据方法
 * @function getDic：获取字典数据;resolveFieldData:根据路径解析数据；getData根据config获取数据 
 * 
 */

    getDic(str: string) {
      let dic = window.localStorage.getItem('dic');
      let dicData = JSON.parse(dic);
      if (!str || !dicData) {
          return []
      }
      if(str == 'root'){
        return dicData
      }
      let dicResult = dicData.find(val => val.code == str);
      return dicResult ? dicResult.dictItems : [];
    }

        //根据数据api获取最终数据
    resolveFieldData(data: any, field: string): any {
      if(data && field) {
          if(field.indexOf('.') == -1) {
              return data[field];
          }
          else {
              let fields: string[] = field.split('.');
              let value = data;
              for(var i = 0, len = fields.length; i < len; ++i) {
                  if (value == null) {
                      return null;
                  }
                  value = value[fields[i]];
              }
              return value;
          }
      }
      else {
          return null;
      }
  }

    getData(metaData){
      if(metaData.isDic){
       metaData.options = this.getDic(metaData.url);
       if(metaData.defaultIndex || metaData.defaultIndex==0){
         setTimeout(()=>this.group.patchValue({[metaData.name]:metaData.justValue?metaData.options[metaData.defaultIndex][metaData.dataKey]:metaData.options[metaData.defaultIndex]}),10)
       }
       return
      }
       this.httpService.post(metaData.url,'')
       .then(data=>{
         if(data.result){
           metaData.options = this.resolveFieldData(data,metaData.dataPath) || [];
           if(metaData.defaultIndex){
             this.group.patchValue({[metaData.name]:metaData.options[metaData.defaultIndex]})
           }
         }
       })
    }
       //表单数据变更 检测 

   onValueChanged(data?){
     if(!this.group || !this.config || !this.configRequired()){return}
     const form = this.group;
     const field = this.config.name
     const control = form.get(field);
     this._errorMessages[field] = '';
     if (control && control.touched && !control.valid) {
      const messages = this.validationMessages? this.validationMessages[field]:null;
      for (const key in control.errors) {
        this._errorMessages[field] = this._errorMessages[field] || {};
        switch(key){
          case 'required':this._errorMessages[field][key]= messages&&messages[key]?messages[key] : (FormRequiredMap[this.config.component] + this.config.title);break;
          case 'pattern':this._errorMessages[field][key]= messages&&messages[key]?messages[key] : (FormRequiredMap[this.config.component] + '正确的' + this.config.title);break;
          case 'repeat':this._errorMessages[field][key] = messages&&messages[key]?messages[key] : `该${this.config.title}已被占用`;break;
          default :this._errorMessages[field][key] = messages&&messages[key]?messages[key] : '验证错误';
        }
      }
    }
  }

  }



/** 
* @type    组件【to User】
* @author  最后修改者:刘天广 
* @version 最后修改版本:v-ng4-1.1.0
* @description ：
           onSubmitted 对外事件驱动
           validationMessages 错误信息对应的map
           getFormValue 返回表单数据【整体】
           submitted 对外接口 可主动调用 提交事件；
*/ 

@Component({
    selector: 'reactive-form',
    styles:[`
    #reactive-form .item-row{display: table-row}
    #reactive-form .item-row:focus,#reactive-table .item-row:focus{outline: none}
    #reactive-form .item-cell{display: table-cell;padding:10px 0}
    #reactive-table .item-all{display: inline-block;margin-right:15px}
    #reactive-table .item-title{display: inline-block;padding-right:10px !important}
    #reactive-table .item-component{display: inline-block;min-width:0 !important ;width:145px}

  `],
    template: `
      <form  [id]="'reactive-' + theme" [formGroup]="form" (ngSubmit)="submit(form.value)" class="form-horizontal" [class.form-all-read]="readOnly" [ngClass]="styleClass">
        <ng-container *ngFor="let item of configToDisplay;index as index" >
         <div class="item-row item-all" [tabindex]="index + 100" [id]="'reactive-form-' + item.name">
                <div [class]="item?.titleClass" [style]="item.titleStyle" [ngClass]="{'item-cell item-title text-right p-r-10 opacity06':true,'p-required':itemRequired(item)}">{{item.title}}：</div>
                <div class="item-cell item-component" [class.form-item-shade]="readOnly&&hasValue(item.name)&&!item.group" 
                [class.form-item-shade-empty]="readOnly&&!hasValue(item.name)&&!item.group"  
                [style.min-width.px]="formMinWidth" [ngClass]="item.componentClass"
                 [ngStyle]="item.componentStyle" [class.icon-text]="item.unit" [attr.data-text]="item.unit">
                    <ng-container *ngIf="item.group && !item.template" >
                      <div *ngFor="let child of item.group;last as last" class="inline-block border-box" [ngClass]="{'p-r-4':!last}" [style.width.%]="100/item.group?.length" [class.form-item-shade]="readOnly&&hasValue(child.name)" [class.form-item-shade-empty]="readOnly&&!hasValue(child.name)">
                          <span reactiveForm [readOnly]="readOnly" [theme]="theme" [validationMessages]="validationMessages" [errorMessages]="formErrors" [config]="child" [group]="form"> </span>
                      </div>
                    </ng-container>
                    <ng-container *ngIf="!item.group && item.name" >
                      <span reactiveForm [readOnly]="readOnly" [theme]="theme" [validationMessages]="validationMessages" [config]="item" [errorMessages]="formErrors" [group]="form"> </span>
                    </ng-container> 
                    <ng-container *ngIf="item.template">
                      <ng-template [pTemplateWrapper]="itemTemplate" [index]="index" [item]="item" *ngIf="itemTemplate"></ng-template>
                    </ng-container>
                    <ng-container *ngIf="item.component == 'label'">
                       <div>
                          {{item.defaultValue}}
                      </div>
                    </ng-container>
                </div>
                <div class="item-cell relative" *ngIf="!item.focusStatus">
                    <p-message  styleClass="p-center" [severity]="(item.errorType || 'error')" [isValue]="true" [text]="formErrors[item.name]"></p-message>
                </div>
         </div>
        </ng-container>
      </form>  
    `
  })
  export class ReactiveFormComponent implements OnInit,AfterContentInit,OnChanges {
    
    @Output() onSubmitted: EventEmitter<any> = new EventEmitter<any>();

    @Input() validationMessages:any;

    @Input() readOnly:boolean;

    @Input() styleClass:any;

    @Input() formMinWidth:any = '260';

    @Input() theme:string = 'form';//values [form、table、dialog、advance]

    public itemTemplate: TemplateRef<any>;

    @ViewChild('itemswrapper') itemsWrapperViewChild: ElementRef;

    @ContentChildren(PrimeTemplate) templates: QueryList<any>;
  
    form: FormGroup;

    dicData: any;

    formErrors:any = {}

    _config: any[] = [];

    configToDisplay:any[] = [];

    submiteed:boolean;

    constructor(private fb: FormBuilder,private httpService:HttpInterceptorService) {

    }

        
    @Input() get config(): any[] {
      return this._config;
    }

    set config(config){
      this._config = config;
      this.configToDisplay = this._config.filter(val=>!val.hidden)
    }

    hasValue(name){
      if(!this.form || !this.readOnly){return ''}
      if(!this.form.get(name)){return ''}
      return this.form.get(name).value;
    }

    itemRequired(item){
      return item.validators && item.validators.includes('required')
    }
  
    ngOnInit() {
      this.form = this.fb.group({});
    }

    ngOnChanges(e){
    }

    ngAfterContentInit() {
      this.templates.forEach((item) => {
          switch(item.getType()) {
              case 'item':
                  this.itemTemplate = item.template;
              break;
              
              default:
                  this.itemTemplate = item.template;
              break;
          }
      });
    }

    reset(){
      this.form.reset()
    }

    submit(val?){
      setTimeout(()=>{
        if(this.form.valid){this.onSubmitted.emit(this.form.getRawValue());return}
        this.submiteed = true;
        this.checkSubmit();
        this.goToError();
      },10)
    }

    pathValue(val:{[key:string]:any}, options?: {onlySelf?: boolean;emitEvent?: boolean;}){
      this.form.patchValue(val,options = {onlySelf:false,emitEvent:false})
    }

    //获取表单数据
    readOnlyData(item){
      if(!this.form || !item || !this.form.get(item.name) || !this.form.get(item.name).value){return ''}
      const value = this.form.get(item.name).value;
       
      if(item.optionLabel){return value[item.optionLabel]}
      return value
    }
    //表单提交检查
    checkSubmit(){
        console.log(this.configToDisplay);
        for (const field of this.configToDisplay) {
        const component = field['component'];
        const name = field['name'];
        if(name && this.form.get(name)){
          const control = this.form.get(name);
          const messages = this.validationMessages? this.validationMessages[name]:null;
            console.log(control);
            console.log(messages);
            if(control.invalid && control.errors){
            Object.keys(control.errors).forEach(key=>{
              this.formErrors[name] = this.formErrors[name] || {};
              switch(key){
                case 'required':this.formErrors[name][key]= messages&&messages[key]? messages[key]: (FormRequiredMap[component] + field.title);break;
                case 'pattern':this.formErrors[name][key]= messages&&messages[key]? messages[key]: (FormRequiredMap[component] + '正确的' + field.title);break;
                case 'repeat':this.formErrors[name][key] = messages&&messages[key]? messages[key]:`该${field.title}已被占用`;break;
                default :this.formErrors[name][key] = messages&&messages[key]? messages[key]:'验证错误';
              }
            })
          }
        }
      }
    }


    goToError(){
      if(!this.formErrors) return
      const controls = this.form.controls;
      for (const field in controls) {
         if(controls[field].errors){
          document.getElementById('reactive-form-'+field).focus();return
         }
      }
    }

    getFormValue(){
      return this.form || {}
    }



  }



@NgModule({
    imports: [SharedModule,ReactiveFormsModule,TreeModule,CommonModule,MessageModule,InputTextModule,DropdownModule,MultiSelectModule,CalendarModule,InputTextareaModule,CheckboxModule,RadioButtonModule,FileUploadModule],
    exports: [SharedModule,ReactiveFormComponent,FormMultiSelectComponent,FormTreeComponent,FormInputComponent,FormCalendarComponent,FormDropdownComponent,FormTextareaComponent,FormCheckboxComponent,FormRadioButtonComponent],
    declarations: [ReactiveFormComponent,FormMultiSelectComponent,FormTreeComponent,ReactiveFormDirective, FormInputComponent,FormCalendarComponent,FormDropdownComponent,FormTextareaComponent,FormCheckboxComponent,FormRadioButtonComponent],
    entryComponents:[FormInputComponent,FormMultiSelectComponent,FormTreeComponent,FormCalendarComponent,FormDropdownComponent,FormTextareaComponent,FormCheckboxComponent,FormRadioButtonComponent]
})
export class ReactiveFormModule { }